package cn.szsys.integration.config.filter;


import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import cn.szsys.integration.EncryptReqData;
import cn.szsys.integration.config.filter.auth.ReqSignParam;
import cn.szsys.integration.config.filter.wrapper.ContentCachingRequestWrapper;
import cn.szsys.integration.constants.AppConstant;
import cn.szsys.integration.ex.BusinessException;
import cn.szsys.integration.ex.enums.ExceptionEnum;
import cn.szsys.integration.repository.AccessorRepository;
import cn.szsys.integration.runner.entity.Accessor;
import cn.szsys.integration.util.CustomValidationUtil;
import cn.szsys.integration.util.EncryptUtil;
import cn.szsys.integration.util.R;
import cn.szsys.integration.util.SignUtil;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Ryan
 * @since 2025-05-08
 **/
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class EncryptionFilter implements Filter {

    private AccessorRepository accessorRepository;

    final AntPathMatcher pathMatcher = new AntPathMatcher();


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        long startTime = System.currentTimeMillis();
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // 读取请求体
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        //校验签名是否传递
        String requestBody = readRequestBody(httpRequest);
        ReqSignParam reqSignParam = getReqSignParam(httpRequest);
        CustomValidationUtil.valid(reqSignParam);

        //将requestId放入到mdc中
        String requestId = reqSignParam.getRequestId();
        if (requestId == null) {
            requestId = IdUtil.objectId();
        }
        MDC.put(AppConstant.REQUEST_ID, requestId);

        //根据ak获取信息
        Accessor accessor = accessorRepository.getAccessorByAk(reqSignParam.getAk());
        log.info("查询出商户,ak: {}, 请求地址: {}", reqSignParam.getAk(), httpRequest.getRequestURI());
        if (null == accessor) {
            throw new BusinessException(ExceptionEnum.TP_AK_WRONG);
        }
        reqSignParam.setAks(accessor.getAks());

        //不是json
        if (request.getContentType() == null || !request.getContentType().contains("application/json")) {
            // 处理请求
//            chain.doFilter(request, response);
//            return;
            throw new BusinessException(ExceptionEnum.WRONG_REQ_METHOD);
        }

        //检查接口权限
        checkPermission(httpRequest, accessor);

        //验签
        if (!SignUtil.validateSign(reqSignParam)) {
            throw new BusinessException(ExceptionEnum.TP_SIGN_WRONG);
        }

        //解密
        String decryptMessage;
        log.info("[解密前]请求报文:{}", requestBody);
        // 解密
        decryptMessage = decrypt(requestBody, accessor);
        log.info("[解密后]请求报文:{}", decryptMessage);

        // 将解密后的请求体重新写入请求
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptMessage.getBytes(StandardCharsets.UTF_8));
        ContentCachingRequestWrapper.DelegatingServletInputStream servletInputStream = new ContentCachingRequestWrapper.DelegatingServletInputStream(byteArrayInputStream);
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(httpRequest, servletInputStream);
        //添加下游名称
        wrappedRequest.addHeader(AppConstant.DOWNSTREAM_ABBREVIATION, accessor.getName());
        //接收到的请求时间
        wrappedRequest.addHeader(AppConstant.START_TIME, "" + startTime);
        // 创建一个包装类来捕获响应体
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(httpResponse);
        // 继续处理请求
        chain.doFilter(wrappedRequest, wrappedResponse);

        // 读取响应体
        byte[] responseBodyBytes = wrappedResponse.getContentAsByteArray();
        String responseBody = new String(responseBodyBytes, StandardCharsets.UTF_8);
        log.info("[加密前]响应报文:{}", responseBody);
        long time = System.currentTimeMillis();
        // 加密响应体
        String encryptedResponseBody = encrypt(responseBody
                , accessor, requestId);
        log.info("[加密后]响应报文:{}", encryptedResponseBody);
        log.info("平台接收请求耗时: {} ms", (time - startTime));


        // 将加密后的响应体写回响应
        byte[] bytes = encryptedResponseBody.getBytes(StandardCharsets.UTF_8);
        httpResponse.setContentLength(bytes.length);
        httpResponse.addHeader(AppConstant.HEADER_TIME, time + "");
        httpResponse.getOutputStream().write(bytes);
        httpResponse.getOutputStream().flush();
        // 确保响应体被复制到原始响应
        wrappedResponse.copyBodyToResponse();
    }

    private String readRequestBody(HttpServletRequest request) throws IOException {
        BufferedReader reader = request.getReader();
        return reader.lines().collect(Collectors.joining(System.lineSeparator()));
    }

    /**
     * 解密
     *
     * @param encryptedText encryptedText
     * @return {@link String}
     * @author Ryan
     */
    private String decrypt(String encryptedText, Accessor accessor) throws BusinessException {
        // 序列化
        EncryptReqData reqData;
        //不是合法的参数
        try {
            reqData = JSON.parseObject(encryptedText, EncryptReqData.class);
        } catch (Exception e) {
            throw new BusinessException(ExceptionEnum.PARAM_NOT_VALID);
        }
        try {
            String decryptData = EncryptUtil.ECB.decode(reqData.getEncryptData(), accessor.getAks());
            return decryptData.replaceAll("\n", "").replaceAll("\r", "");
        } catch (BusinessException e) {
            log.error("解密请求报文异常,e:", e);
            throw e;
        }
    }

    /**
     * 加密
     *
     * @param plainText plainText
     * @param accessor  accessor
     * @param requestId requestId
     * @return {@link String}
     * @author Ryan
     */
    private String encrypt(String plainText, Accessor accessor, String requestId) {
        try {
            R r = JSON.parseObject(plainText, R.class);
            Object data = r.getData();
            if (data != null) {
                String dataEncrypt = EncryptUtil.ECB.encode(JSON.toJSONString(data), accessor.getAks());
                r.setData(dataEncrypt);
            }
            r.setRequestId(requestId);
            return r.toString();
        } catch (Exception ignore) {
        }
        return plainText;
    }

    /**
     * 获取请求头或者参数
     *
     * @param request request
     * @return {@link ReqSignParam}
     * @author Ryan
     */
    private ReqSignParam getReqSignParam(HttpServletRequest request) {
        ReqSignParam reqSignParam = new ReqSignParam();
        String tmp = getHeaderOrParam(request, "sign");
        reqSignParam.setSign(tmp);

        String ak = getHeaderOrParam(request, "ak");
        reqSignParam.setAk(ak);

        String time = getHeaderOrParam(request, "time");
        reqSignParam.setTime(CharSequenceUtil.isBlank(time) ? null : Long.parseLong(time));

        String alg = getHeaderOrParam(request, "alg");
        reqSignParam.setAlg(CharSequenceUtil.isBlank(alg) ? null : Integer.parseInt(alg));

        String ver = getHeaderOrParam(request, "ver");
        reqSignParam.setVer(CharSequenceUtil.isBlank(ver) ? null : Integer.parseInt(ver));

        String requestId = getHeaderOrParam(request, "requestId");
        reqSignParam.setRequestId(requestId);
        return reqSignParam;
    }


    /**
     * 从header中或者parameterMap中获取解密数据
     *
     * @param req req
     * @param key key
     * @return {@link String}
     * @author Ryan
     */
    private String getHeaderOrParam(HttpServletRequest req, String key) {
        String val = req.getHeader(key);
        if (CharSequenceUtil.isBlank(val)) {
            val = req.getParameter(key);
        }
        return val;
    }

    /**
     * 检查接口权限
     *
     * @param req      req
     * @param accessor accessor
     * @author Ryan
     */
    private void checkPermission(HttpServletRequest req, Accessor accessor) {
        String requestUri = req.getRequestURI();
        Set<String> apiPermissions = accessor.getApiPermissions();
        boolean hasPermission = false;

        for (String apiPermission : apiPermissions) {
            String replace = apiPermission.replace("**", "");
            if (requestUri.startsWith(replace)) {
                hasPermission = true;
                break;
            }
        }

        if (!hasPermission) {
            throw new BusinessException(ExceptionEnum.TP_PERMISSION_DENIED);
        }
    }
}
